/*
 * @Author: 13177709051 3506463075@qq.com
 * @Date: 2024-12-02 14:46:08
 * @LastEditors: 13177709051 3506463075@qq.com
 * @LastEditTime: 2024-12-02 15:41:09
 * @FilePath: /example/socket.cpp
 * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
 */
#include <iostream>
#include <cstdio>
#include <string>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/socket.h>

int main()
{
    // 1. 创建套接字
    int sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (sockfd < 0)
    {
        perror("socket error!");
        return -1;
    }
    // socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)：创建一个套接字。
    // AF_INET：指定IPv4地址族。
    // SOCK_STREAM：指定使用流套接字（TCP协议）。
    // IPPROTO_TCP：使用TCP协议。
    // 如果套接字创建失败，socket()函数返回-1，使用perror打印错误信息。
    
    // 2. 绑定
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(8080);
    addr.sin_addr.s_addr = inet_addr("0.0.0.0");
    socklen_t len = sizeof(struct sockaddr_in);
    int ret = bind(sockfd, (struct sockaddr *)&addr, len);
    if (ret < 0)
    {
        perror("bind error!");
        return -1;
    }

    // struct sockaddr_in：这是一个用于IPv4地址的结构体。它包含了地址族、端口号和IP地址。
    //     sin_family：指定地址族为IPv4（AF_INET）。
    //     sin_port：指定端口号，这里是8080，使用htons()进行字节顺序转换（因为网络字节序是大端字节序）。
    //     sin_addr.s_addr：设置服务器的IP地址为0.0.0.0，意味着监听所有可用的网络接口。
    // bind()：将套接字与指定的地址进行绑定，bind()函数将服务器的地址信息与套接字关联。
    // 如果绑定失败，bind()函数返回-1，使用perror打印错误信息。

    // 3. 监听连接
    ret = listen(sockfd, 5);
    if (ret < 0)
    {
        perror("listen error!");
        return -1;
    }

    // listen()：设置套接字为被动模式，开始监听连接请求。
    //     第一个参数是套接字描述符。
    //     第二个参数是等待连接的最大队列长度，这里设置为5。表示在连接队列满时，新来的连接请求将被拒绝。
    // 如果监听失败，listen()返回-1，使用perror打印错误信息。

    while (1)
    {
        // 4. 监听客户端连接
        int newfd = accept(sockfd, NULL, 0);
        if (newfd < 0)
        {
            perror("accpet error!");
            return -1;
        }

        // accept()：用于接受客户端的连接请求。
        //     第一个参数是监听套接字（sockfd）。
        //     第二个和第三个参数是客户端地址的信息，这里没有使用（NULL和0）。
        // accept()成功返回一个新的套接字描述符newfd，用于与客户端进行通信。若连接失败，则返回-1。

        // 5. 接收客户端数据
        char buf[1024] = {0};
        int ret = recv(newfd, buf, 1024, 0);
        if (ret < 0)
        {
            perror("recv error!");
            close(newfd);
            continue;
        }
        else if (ret == 0)
        {
            perror("peer shutdown!");
            close(newfd);
            continue;
        }

        // recv()：从newfd套接字接收数据。将接收到的数据存储在buf缓冲区中，最多接收1024字节。
        // recv()返回值：
        // 若返回负值，表示接收出错。
        // 若返回0，表示对方关闭了连接。
        // 在发生错误或对方关闭连接时，关闭newfd套接字，继续等待下一个连接。

        // 6. 构造并发送HTTP响应
        std::string body = "<html><body><h1>Hello World! 666<html><body><h1>";
        std::string rsp = "HTTP/1.1 200 OK\r\n";
        rsp += "Content-Length: " + std::to_string(body.size()) + "\r\n";
        rsp += "Content-Type : text/html\r\n";
        rsp += "\r\n";
        rsp += body;

        // body：HTML响应内容，这里是一个简单的HTML页面，包含一个标题 "Hello World!"。
        // rsp：构造一个标准的HTTP响应头，格式如下：
        // HTTP/1.1 200 OK：表示响应的状态码为200，表示成功。
        // Content-Length：内容长度（即body的字节大小）。
        // Content-Type：表示返回内容的类型，这里是HTML (text/html)。
        // 空行 \r\n：表示响应头结束，接下来是响应体。
        // 最后，将HTML内容body附加到响应字符串后。
        
        // 7. 发送HTTP响应
        ret = send(newfd, rsp.c_str(), rsp.size(), 0);
        if (ret < 0)
        {
            perror("send perror!");
            close(newfd);
            continue;
        }

        // send()：将构造好的HTTP响应发送给客户端。
        // 第一个参数是与客户端通信的套接字newfd。
        // 第二个参数是要发送的数据，rsp.c_str()返回字符串的C风格指针。
        // 第三个参数是要发送的数据长度。
        // 第四个参数是标志，这里使用0。
        // 如果发送数据失败，send()返回负值，使用perror打印错误信息并关闭newfd。


        // 8. 关闭客户端连接
        close(newfd);
        // close(newfd)：关闭与客户端的连接，释放套接字资源。然后继续等待下一个连接。
    }
    // 9. 关闭监听套接字
    close(sockfd);
    // close(sockfd)：关闭监听套接字，释放资源
    return 0;
}